﻿-- this file must be encoded by UTF-8 and be run by LuaJIT 2
-- 导入《俺たちに翼はない》的sob文件中字符串的程序 (by dwing)

local ffi = require("ffi")
ffi.cdef[[
int __stdcall MultiByteToWideChar(int cp, int flag, const char* src, int srclen, char* dst, int dstlen);
int __stdcall WideCharToMultiByte(int cp, int flag, const char* src, int srclen, char* dst, int dstlen, const char* defchar, int* used);
]]
local C = ffi.C

local byte = string.byte
local char = string.char
local floor = math.floor
local format = string.format

local fsrc, fdst
local fsrcp = 0

local function Exit()
	if fdst then fdst:close() end
	if fsrc then fsrc:close() end
	os.exit(0)
end

local function Assert(x, ...)
	if x then return x, ... end
	print("ERROR: " .. ...)
	Exit()
end

local function Str_Int(s, p)
	return byte(s, p+1) + byte(s, p+2) * 256 + byte(s, p+3) * 65536 + byte(s, p+4) * 256 * 65536
end

local function Int_Str(i)
	return char(i % 256, floor(i / 256) % 256, floor(i / 65536) % 256, floor(i / 65536 / 256) % 256)
end

local function MB_WC(src, cp)
	local srclen = #src
	local dstlen = srclen * 2
	local dst = ffi.new("char[?]", dstlen)
	dstlen = C.MultiByteToWideChar(cp or 0, 0, src, srclen, dst, dstlen) * 2
	return ffi.string(dst, dstlen)
end

local function WC_MB(src, cp)
	local srclen = floor(#src / 2)
	local dstlen = srclen * 3
	local dst = ffi.new("char[?]", dstlen)
	dstlen = C.WideCharToMultiByte(cp or 0, 0, src, srclen, dst, dstlen, nil, nil)
	return ffi.string(dst, dstlen)
end

local function JIS_UTF8(s)
	return WC_MB(MB_WC(s, 932), 65001)
end

local function UTF8_JIS(s)
	return WC_MB(MB_WC(s, 65001), 932)
end

local function UTF8_GBK(s)
	return WC_MB(MB_WC(s, 65001), 936)
end

local function JIS_GBK(s)
	return WC_MB(MB_WC(s, 932), 936)
end

local function Main()
	if not arg[3] then
		print("usage: lua modsob.lua <input-filename.sob> <input-filename.txt> <output-filename.sob>")
		Exit()
	end

	io.stdout:write(arg[1] .. " + " .. arg[2] .. " => " .. arg[3] .. " ... ")

	fsrc = Assert(io.open(arg[2], "r"))
	local src = Assert(fsrc:read("*a"))
	fsrc:close()
	fsrc = nil

	local txt, idx = {}, {}
	for i, s, j, d in src:gmatch("○(%x+)○([^\n]*).-●(%x+)●([^\n]*)") do
		if i ~= j then
			print(UTF8_GBK("ERROR: unmatched tag: ○" .. i .. "○ and ●" .. j .. "●"))
			Exit()
		else--if s ~= d then
			i = tonumber("0x" .. i)
			if not i then
				print("ERROR: not hex number tag: " .. j)
				Exit()
			end
			if txt[i] then
				print("ERROR: duplicated tag: " .. i)
				Exit()
			end
			txt[i] = { src = UTF8_JIS(s), dst = UTF8_GBK(d) }
			idx[#idx + 1] = i
		end
	end
	table.sort(idx)
	local delta_max = 0
	for _, i in ipairs(idx) do
		txt[i].delta = delta_max
		delta_max = delta_max + #txt[i].dst - #txt[i].src
	end
	io.stdout:write("[" .. #idx .. "] ... ")
	if #idx <= 0 then
		print("SKIP!")
		Exit()
	end

	fsrc = Assert(io.open(arg[1], "rb"))
	local src = Assert(fsrc:read("*a"))
	fsrc:close()
	fsrc = nil

	fdst = Assert(io.open(arg[3], "wb"))
	local base = Str_Int(src, 4) + 16
	Assert(fdst:write(src:sub(1, 16)))
	for i = 16, base - 16, 8 do
		local p = Str_Int(src, i)
		local d = delta_max
		for _, j in ipairs(idx) do
			if base + p <= j then
				d = txt[j].delta
				break
			end
		end
		Assert(fdst:write(Int_Str(p + d)))
		Assert(fdst:write(src:sub(i + 5, i + 8)))
	end
	Assert(fdst:write(src:sub(base - 7, base - 4)))
	Assert(fdst:write(Int_Str(Str_Int(src, base - 4) + delta_max)))

	local p = base + 1
	for _, i in ipairs(idx) do
		if i < p - 1 then
			print(format("ERROR: overlapping string at %08X < %08X", i, p - 1))
			Exit()
		end
		Assert(fdst:write(src:sub(p, i))); p = i + 1
		local s = txt[i].src
		local d = txt[i].dst
		if src:sub(p, p + #s - 1) ~= s then
			print(format("ERROR: unmatched string at %08X [%08X]", i, #s))
			print("src: " .. JIS_GBK(src:sub(p, p + #s - 1)))
			print("mod: " .. JIS_GBK(s))
			Exit()
		end
		Assert(fdst:write(d)); p = p + #s
	end
	Assert(fdst:write(src:sub(p)))

	print("OK!")
	Exit()
end

Main()
